﻿using System;
using System.Diagnostics.Contracts;
using System.Threading;

namespace O1.Storage
{
    [ContractVerification(true)]
    internal class DifferencingStore
    {
        private const int PaddingSize = 1024;

        private const double MergeThreshold = .25F;

        private readonly BlockArray<byte[][]> storage;

        public DifferencingStore(long initialCapacity)
        {
            this.storage = new BlockArray<byte[][]>(initialCapacity);
        }

        public StorageResults Save(long id, byte[] change)
        {
            var s = this.storage;
            if (id >= s.Length)
            {
                s.Expand(id + PaddingSize);
            }

            var cell = s[id];
            if (cell == null)
            {
                cell = new byte[2][];
                cell[1] = change;
                s[id] = cell;
                return StorageResults.WrittenAsDelta | StorageResults.ShouldMerge;
            }

            var differences = cell[1];
            var copy = new byte[change.Length + differences.Length];
            Buffer.BlockCopy(differences, 0, copy, change.Length, differences.Length);
            Buffer.BlockCopy(change, 0, copy, 0, change.Length);
            cell[1] = copy;
            return StorageResults.WrittenAsDelta | CheckForMerge(cell[0], copy.Length);
        }

        private static StorageResults CheckForMerge(byte[] cell, int differencesLenth)
        {
            // The merge process has to be smart to recognize committed changes only when truncating
            // change history. How to indicate merged records that can be dropped?
            // Only need to know the change transaction ID and size. So if all delta records have
            // TxId and Size as a header, this can be generalized.
            if (cell == null)
            {
                return StorageResults.ShouldMerge;
            }

            if (cell.Length * MergeThreshold > differencesLenth)
            {
                return StorageResults.ShouldMerge;
            }

            return StorageResults.None;
        }
    }
}